#ifndef LENGTH_FIELD_H
#define LENGTH_FIELD_H

#include <iostream>

template<int NumBytes> class LengthField
{
public:
	enum
	{
		EscapeValue = ( 1 << ( 8 * NumBytes ) ) - 1, // NumBytes bytes filled with 0xff.
	};
	LengthField() : mValue( 0 ) {}
	LengthField( size_t value ) : mValue( value ) {}

	// Type conversion:
	operator size_t&() { return mValue; }

	// Formatted (ASCII) I/O:
	std::ostream& WriteToStream( std::ostream& os ) const { return os << mValue; }
	std::istream& ReadFromStream( std::istream& is ) { return is >> mValue; }

	// Binary I/O according to the BCI2000 protocol:
	std::ostream& WriteBinary( std::ostream& os ) const;
	std::istream& ReadBinary( std::istream& is );

private:
	size_t mValue;
};

template<int NumBytes> std::ostream&
	operator<<( std::ostream& os, const LengthField<NumBytes>& l )
{
	return l.WriteToStream( os );
}

template<int NumBytes> std::istream&
	operator>>( std::istream& is, LengthField<NumBytes>& l )
{
	return l.ReadFromStream( is );
}

template<int NumBytes> std::ostream&
	LengthField<NumBytes>::WriteBinary( std::ostream& os ) const
{
	size_t value = mValue;
	if( value > EscapeValue )
		value = EscapeValue;
	// Old protocol: Write bytes of mValue in little endian order.
	// Extended protocol: Write the escape value.
	for( int i = 0; i < NumBytes; ++i )
	{
		os.put( value & 0xff );
		value >>= 8;
	}
	if( mValue >= EscapeValue )
	{
		// Extended protocol: Write a null-terminated ASCII representation of the
		// value after the escape value.
		os << mValue;
		os.put( '\0' );
	}
	return os;
}

template<int NumBytes> std::istream&
	LengthField<NumBytes>::ReadBinary( std::istream& is )
{
	mValue = 0;
	// Read a little endian value according to the old protocol.
	for( int i = 0; i < NumBytes; ++i )
		mValue |= ( is.get() << ( 8 * i ) );
	// If the value matches the escape value, read a null-terminated
	// ASCII representation.
	if( mValue == EscapeValue )
	{
		is >> mValue;
		if( is.get() != '\0' )
			is.setstate( is.failbit );
	}
	return is;
}

#endif // LENGTH_FIELD_H